#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define NUM_PHILOSOPHER 5
#define NUM_TAKE 3

bool chopstick[NUM_PHILOSOPHER];
int sem_id;

int custom_sem_trywait(int number) {
    struct sembuf sem;
    sem.sem_num = number;
    sem.sem_op = -1;
    sem.sem_flg = IPC_NOWAIT;
    return semop(sem_id, &sem, 1);
}

int custom_sem_post(int number) {
    struct sembuf sem;
    sem.sem_num = number;
    sem.sem_op = 1;
    sem.sem_flg = 0;
    return semop(sem_id, &sem, 1);
}

void take_chopsticks(int number) {
    int chopstick_taked;
    while (true) {
        chopstick_taked = custom_sem_trywait(number);
        if (chopstick_taked == 0) {
            chopstick_taked = custom_sem_trywait((number + 1) % NUM_PHILOSOPHER);
            if (chopstick_taked == 0) {
                chopstick[number] = false;
                chopstick[(number + 1) % NUM_PHILOSOPHER] = false;
                break;
            }
            else {
                custom_sem_post(number);
            }
        }
    }
}

void put_chopsticks(int number) {
    chopstick[number] = true;
    chopstick[(number + 1) % NUM_PHILOSOPHER] = true;
    custom_sem_post(number);
    custom_sem_post((number + 1) % NUM_PHILOSOPHER);
}

void *philosopher(void *arg) {
    int philosopher_id = *(int*)arg;
    for (int i = 0; i < NUM_TAKE; i++) {
        printf("philosopher %d is thinking\n", philosopher_id);
        sleep(1);
        take_chopsticks(philosopher_id);
        printf("philosopher %d is eating\n", philosopher_id);
        sleep(1);
        put_chopsticks(philosopher_id);
    }
    return NULL;
}

int main() {
    key_t key = ftok(".", 'x');
    sem_id = semget(key, NUM_PHILOSOPHER, IPC_CREAT | 0666);
    for (int i = 0; i < NUM_PHILOSOPHER; i++) {
        semctl(sem_id, i, SETVAL, 1);
    }

    memset(chopstick, 1, sizeof(bool) * NUM_PHILOSOPHER);
    int index[NUM_PHILOSOPHER];
    for (int i = 0; i < NUM_PHILOSOPHER; i++) {
        index[i] = i;
    }

    pthread_t tids[NUM_PHILOSOPHER];
    for (int i = 0; i < NUM_PHILOSOPHER; i++) {
        pthread_create(&tids[i], NULL, philosopher, (void*)&index[i]);
    }
    for (int i = 0; i < NUM_PHILOSOPHER; i++) {
        pthread_join(tids[i], NULL);
    }


    return 0;
}
